{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Part 8 - Plans入門\n",
    "\n",
    "\n",
    "### コンテキスト \n",
    "\n",
    "Federated Learningを大規模に実運用しようと思った際に重要なオブジェクトとなる、Planという概念について紹介します。Planは使用するネットワーク帯域を劇的に減らし、非同期処理を実現し、リモートデイバイスに自律性を与えてくれます。元となるアイデアはこの論文、[Towards Federated Learning at Scale: System Design](https://arxiv.org/pdf/1902.01046.pdf)、を参照してください。現在はPySyftライブラリのニーズに応じて、一部変更が加えられています。\n",
    "\n",
    "Planは、関数のように、連続するオペレーションを纏める目的で作られています。しかし、Planを使えば、定義した一連のオペレーションを一回のメッセージでリモートのワーカーに送ることができます。こうすることで、N個の（オペレーションの）メッセージを送る代わりに1つのメッセージを送るだけで、ポインタを通してN個のオペレーションを参照できます。PlanにはTensor(\"_state tensors_\"と呼ばれます)をつけて送ることもできます。\"_state tensors_\"は引数のようなものです。Planは送信可能な関数と捉えることもできますし、リモートにて実行可能なクラスと捉えることもできます。これによって、高次のユーザーはPlanの概念を特に意識することなく、恣意的な連続するPyTorchの関数をリモートワーカーに送ることが可能になります。\n",
    "\n",
    "一点注意が必要な点は、現時点ではPlanで使用可能な関数はPyTorchのHook機能を持つオペレーションに限定されています。これは `if`, `for` そしrて `while` といった論理構造が使えないことを意味します。私たちはこの件について対応中です。\n",
    "\n",
    "正確には、これらのオペレーションを使うことはできますが、最初のコンピューテーションで取った分岐がその後の全てのコンピューテーションに適応されてしまいます。これでは都合が悪いですよね。\n",
    "\n",
    "Authors:\n",
    "- Théo Ryffel - Twitter [@theoryffel](https://twitter.com/theoryffel) - GitHub: [@LaRiffle](https://github.com/LaRiffle)\n",
    "- Bobby Wagner - Twitter [@bobbyawagner](https://twitter.com/bobbyawagner) - GitHub: [@robert-wagner](https://github.com/robert-wagner)\n",
    "- Marianne Monteiro - Twitter [@hereismari](https://twitter.com/hereismari) - GitHub: [@mari-linhares](https://github.com/mari-linhares)\t"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### インポートとモデル定義\n",
    "\n",
    "まずは、通常のPyTorchのインポート処理を行いましょう"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "次にPySyft用のコードです。一つ覚えておくべきことは、**ローカルワーカーはクライアントワーカーになるべきではないという事です。** クライアンワーカー以外はPlanの実行に必要なオブジェクトを保持できません。ここで言うローカルワーカーとは私たちで、クライアントワーカーとはリモートワーカーの事です。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import syft as sy  # Pysyftライブラリをインポート\n",
    "hook = sy.TorchHook(torch)  # PyTorchをホック ie torchを拡張します\n",
    "\n",
    "# IMPORTANT: ローカルワーカーはクライアントワーカーになることは出来ません\n",
    "hook.local_worker.is_client_worker = False\n",
    "\n",
    "server = hook.local_worker"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "説明の通り、リモートワーカー（デバイス）を定義します。\n",
    "そして、彼らにデータを割り当てます。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x11 = torch.tensor([-1, 2.]).tag('input_data')\n",
    "x12 = torch.tensor([1, -2.]).tag('input_data2')\n",
    "x21 = torch.tensor([-1, 2.]).tag('input_data')\n",
    "x22 = torch.tensor([1, -2.]).tag('input_data2')\n",
    "\n",
    "device_1 = sy.VirtualWorker(hook, id=\"device_1\", data=(x11, x12)) \n",
    "device_2 = sy.VirtualWorker(hook, id=\"device_2\", data=(x21, x22))\n",
    "devices = device_1, device_2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 基本的な例\n",
    "\n",
    "では、Planとして纏めたい関数を定義しましょう。そのために行うことは、ほとんど関数の上にデコレータを記述するだけです。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@sy.func2plan()\n",
    "def plan_double_abs(x):\n",
    "    x = x + x\n",
    "    x = torch.abs(x)\n",
    "    return x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Planが作成できました。チェックしてみましょう。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plan_double_abs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Planの使用には2つのステップがあります。まず、ビルド（これは関数の中の連続するオペレーションを登録するような事です）です。次にビルドしたPlanをワーカー（デバイス）へ送ります。簡単な作業で実現できます。\n",
    "\n",
    "#### Planのビルド\n",
    "\n",
    "Planをビルドするには、何かしらデータを付けてコールしてあげるだけでOKです。\n",
    "まずは、リモートデータの参照を取得しましょう。参照取得のリクエストはネットワーク越しに送信され、ポインタが返されます。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_to_data = device_1.search('input_data')[0]\n",
    "pointer_to_data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "もし、ここで`location:device_1`のデバイス上でPlayを実行しようとすると、エラーになってしまいます。まだビルドが出来ていないからです。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plan_double_abs.is_built"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# ビルドされていないPlanをリモートワーカーへ送ろうとするとエラーになります\n",
    "try:\n",
    "    plan_double_abs.send(device_1)\n",
    "except RuntimeError as error:\n",
    "    print(error)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Planをビルドするには、必要な引数（何らかのデータ）を渡しつつ`build`コマンドを実行してください。Planがビルドされると全てのコマンドはローカルワーカーによって、順番に実行され、結果はPlayの`actions`属性にキャッシュされます。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plan_double_abs.build(torch.tensor([1., -2.]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plan_double_abs.is_built"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "このPlayを再度送ってみましょう。今度はうまく行きます。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 今回はエラーは出ません\n",
    "pointer_plan = plan_double_abs.send(device_1)\n",
    "pointer_plan"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tensorの時と同様にポインタが取得できます。`PointerPlan`という名前は分かり易いですね。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "特筆すべきことの一つは、Planはビルドされる時にコンピューテーションの結果として割り当てられるIDが事前に設定されます。これにより、リモートマシンのコンピューテーションの結果を待たずしてIDを取得でき、コマンドは非同期で送信が可能になります。例えば、device_1でのバッチ処理の結果を待たずにdevice_2で次のバッチ処理を実行することが可能になります。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Planをリモートで実行する\n",
    "\n",
    "\n",
    "ポインタにデータを引数として渡す事により、Planをリモート環境で実行することができます。Plan実行結果は事前に定義された場所（結果格納場所はコンピューテーションの前に事前に設定されます）に格納されます。\n",
    "結果はシンプルにポインタです。他のPyTorchのオペレーションをリモートで実行した時と同じです。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_to_result = pointer_plan(pointer_to_data)\n",
    "print(pointer_to_result)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "そして、計算された値は、今までと同様の手法で受け取ることが可能です。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_to_result.get()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### もう少し実践的な例\n",
    "\n",
    "ところで、私たちがやりたいのは、PlanをFederated Learningを使ったディープラーニングに応用することですよね。では、ニューラルネットを扱かったもう少しだけ複雑な例をみてみましょう。\n",
    "\n",
    "注記: 私たちは通常のクラスをPlanへ変更しています。これは`nn.Module`のかわりに`sy.Plan`を継承することで実現できます。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Net(sy.Plan):\n",
    "    def __init__(self):\n",
    "        super(Net, self).__init__()\n",
    "        self.fc1 = nn.Linear(2, 3)\n",
    "        self.fc2 = nn.Linear(3, 2)\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = F.relu(self.fc1(x))\n",
    "        x = self.fc2(x)\n",
    "        return F.log_softmax(x, dim=0)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net = Net()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "ダミーデータを使ってビルドしてみましょう。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net.build(torch.tensor([1., 2.]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "次に、Planをリモートワーカーへ送ってみましょう"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_to_net = net.send(device_1)\n",
    "pointer_to_net"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "リモートデータのポインタを取得しましょう"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_to_data = device_1.search('input_data')[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "構文的にはリモートマシン上で逐次的にオペレーションを実行するのと何らかわりはありません。ですが、この手法では複数のオペレーションが一回のコミュニケーションで実行されています。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_to_result = pointer_to_net(pointer_to_data)\n",
    "pointer_to_result"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "データの受け取りはいつも通りです。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_to_result.get()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "ジャジャーン！ローカルワーカー（サーバー、この場合は私たち？）とリモートデバイスの間のコミュニケーションを劇的に減らすことに成功しました！"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ワーカー間での使い回し\n",
    "\n",
    "私たちが欲しい重要な機能の一つは、一つのPlanを複数のワーカー間で使い回すことです。\n",
    "得に、新しいワーカーのためにイチイチPlanをビルドするのは避けたいですよね。\n",
    "どうすれば良いか、先ほどのニューラルネットを例に、やってみましょう。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Net(sy.Plan):\n",
    "    def __init__(self):\n",
    "        super(Net, self).__init__()\n",
    "        self.fc1 = nn.Linear(2, 3)\n",
    "        self.fc2 = nn.Linear(3, 2)\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = F.relu(self.fc1(x))\n",
    "        x = self.fc2(x)\n",
    "        return F.log_softmax(x, dim=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net = Net()\n",
    "\n",
    "# Planのビルド\n",
    "net.build(torch.tensor([1., 2.]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "メインのステップです。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_to_net_1 = net.send(device_1)\n",
    "pointer_to_data = device_1.search('input_data')[0]\n",
    "pointer_to_result = pointer_to_net_1(pointer_to_data)\n",
    "pointer_to_result.get()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "実は、同じPlanから別のPointerPlansをビルドすることが可能です。別のデバイスでPlanをリモート実行する時と同じです。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_to_net_2 = net.send(device_2)\n",
    "pointer_to_data = device_2.search('input_data')[0]\n",
    "pointer_to_result = pointer_to_net_2(pointer_to_data)\n",
    "pointer_to_result.get()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注記: この例では、Planは一つのオペレーションしか実行しています。実行されていたのは`forward`です。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Planの自動ビルド\n",
    "\n",
    "`@` `sy.func2plan`をつけることで、Planを自動的にビルドすることが可能です。この場合、Planは定義と同時にビルドされるので、明示的に`build`する必要はありません。\n",
    "\n",
    "この機能を有効にするために必要なことは、`args_shape`という名前の引数を渡すことだけです；"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@sy.func2plan(args_shape=[(-1, 1)])\n",
    "def plan_double_abs(x):\n",
    "    x = x + x\n",
    "    x = torch.abs(x)\n",
    "    return x\n",
    "\n",
    "plan_double_abs.is_built"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`args_shape`はPlanをビルドする際にダミーデータを作成するのに使われます。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@sy.func2plan(args_shape=[(1, 2), (-1, 2)])\n",
    "def plan_sum_abs(x, y):\n",
    "    s = x + y\n",
    "    return torch.abs(s)\n",
    "\n",
    "plan_sum_abs.is_built"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`state`引数を使って実データを渡すことも可能です。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@sy.func2plan(args_shape=[(1,)], state=(torch.tensor([1]), ))\n",
    "def plan_abs(x, state):\n",
    "    bias, = state.read()\n",
    "    x = x.abs()\n",
    "    return x + bias"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pointer_plan = plan_abs.send(device_1)\n",
    "x_ptr = torch.tensor([-1, 0]).send(device_1)\n",
    "p = pointer_plan(x_ptr)\n",
    "p.get()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "もっと知りたい方はチュートリアルPart 8 bisを参照してください。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### PySyftのGitHubレポジトリにスターをつける\n",
    "\n",
    "一番簡単に貢献できる方法はこのGitHubのレポジトリにスターを付けていただくことです。スターが増えると露出が増え、より多くのデベロッパーにこのクールな技術の事を知って貰えます。\n",
    "\n",
    "- [Star PySyft](https://github.com/OpenMined/PySyft)\n",
    "\n",
    "### Slackに入る\n",
    "\n",
    "最新の開発状況のトラッキングする一番良い方法はSlackに入ることです。\n",
    "下記フォームから入る事ができます。\n",
    "[http://slack.openmined.org](http://slack.openmined.org)\n",
    "\n",
    "### コードプロジェクトに参加する\n",
    "\n",
    "コミュニティに貢献する一番良い方法はソースコードのコントリビューターになることです。PySyftのGitHubへアクセスしてIssueのページを開き、\"Projects\"で検索してみてください。参加し得るプロジェクトの状況を把握することができます。また、\"good first issue\"とマークされているIssueを探す事でミニプロジェクトを探すこともできます。\n",
    "\n",
    "- [PySyft Projects](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3AProject)\n",
    "- [Good First Issue Tickets](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)\n",
    "\n",
    "### 寄付\n",
    "\n",
    "もし、ソースコードで貢献できるほどの時間は取れないけど、是非何かサポートしたいという場合は、寄付をしていただくことも可能です。寄附金の全ては、ハッカソンやミートアップの開催といった、コミュニティ運営経費として利用されます。\n",
    "\n",
    "[OpenMined's Open Collective Page](https://opencollective.com/openmined)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
